#pragma once

#include "Code\StringConvert.h"

class CIniDisposer
{
public:
	virtual BOOL ReceiveSection(CString strSection) = 0;
	virtual BOOL ReceiveValue(CString strKey, CString strValue) = 0;
};

class CIniParser
{
public:
	typedef hash_map<CString, CString> INIMAP;
	typedef INIMAP::iterator INIMAP_IT;

	BOOL ParseIniFile(LPCTSTR lpszIniFileName);
	void ParseIniInMem(LPVOID pIniData, unsigned int nBytes);
	BOOL ParseIniInResource(HMODULE hModule, LPCTSTR lpName, LPCTSTR lpType);

	UINT GetProfileInt(LPCTSTR lpSection, LPCTSTR lpKeyName, INT nDefault);
	LPCTSTR GetProfileString(LPCTSTR lpSection, LPCTSTR lpKeyName, LPCTSTR lpDefault = NULL);
	void GetProfileString(LPCTSTR lpSection, LPCTSTR lpKeyName, LPCTSTR lpDefault, LPTSTR lpReturnedString,	DWORD nSize);

	UINT GetProfileInt(LPCTSTR lpSection, LPCTSTR lpKeyName, INT nDefault, CFile *pFile);
	CString GetProfileString(LPCTSTR lpSection, LPCTSTR lpKeyName, LPCTSTR lpDefault, CFile *pFile);

	CIniParser(void);
	~CIniParser(void);

private:

	void Parse(CFile *pFile, CIniDisposer *pDisposer);

	CString FetchValue(LPCTSTR lpSection,LPCTSTR lpKeyName, CFile *pFile = NULL);

	template<typename CHAR_TYPE>
	void Parse(CFile *pFile, CIniDisposer *pDisposer, const CHAR_TYPE);

	template<typename STRING_TYPE>
	BOOL GetLine(CFile *pFile, STRING_TYPE &rString);

	template<typename CHAR_TYPE>
	CHAR_TYPE *GetLine(CFile *pFile, CHAR_TYPE *lpsz, UINT nMax);

	template<typename STRING_TYPE>
	CString ConvertString(STRING_TYPE &rString);

	inline int GetStringLength(const char *lpsz)
	{
		return lstrlenA(lpsz);
	}

	inline int GetStringLength(const wchar_t *lpsz)
	{
		return lstrlenW(lpsz);
	}

private:
	INIMAP  dict;    //dictionary!
};

template<typename CHAR_TYPE>
void CIniParser::Parse(CFile *pFile, CIniDisposer *pDisposer, const CHAR_TYPE)
{
	typedef CStringT<CHAR_TYPE, StrTraitMFC_DLL<CHAR_TYPE>> STRING_TYPE;

	STRING_TYPE  strline;  //saves per line from INI file 
	STRING_TYPE  strSection ;  //saves section+key
	while (GetLine(pFile,strline))
	{
		if (!strline.IsEmpty())
		{
			int beginPos = strline.Find('[');
			int endPos = strline.ReverseFind(']');
			int keyPos = strline.Find('='); 

			if (beginPos != -1 && endPos != -1)
			{
				strSection = strline.Mid(beginPos+1 ,endPos-beginPos-1);
				strSection.Trim();
				if(!pDisposer->ReceiveSection(ConvertString(strSection)))
				{
					continue;
				}
			}
			if (keyPos != -1)
			{
				STRING_TYPE strKey = strline.Mid(0,keyPos);;
				STRING_TYPE strValue = strline.Mid(keyPos+1,strline.GetLength()-keyPos);
				strKey.Trim();

				if(!pDisposer->ReceiveValue(ConvertString(strKey), ConvertString(strValue)))
				{
					break;
				}
			}
		}
	}
}

template<typename STRING_TYPE>
BOOL CIniParser::GetLine(CFile *pFile, STRING_TYPE &rString)
{
	typedef STRING_TYPE::XCHAR CHAR_TYPE;

	rString.Empty();    // empty string without deallocating
	const int nMaxSize = 128;
	CHAR_TYPE *lpsz = rString.GetBuffer(nMaxSize);
	CHAR_TYPE *lpszResult;
	int nLen;
	for (;;)
	{
		lpszResult = GetLine(pFile, lpsz, (UINT)-nMaxSize); // store the newline
		rString.ReleaseBuffer();

		// if string is read completely or EOF
		if (lpszResult == NULL ||
			(nLen = GetStringLength(lpsz)) < nMaxSize ||
			lpsz[nLen-1] == '\n')
		{
			break;
		}

		nLen = rString.GetLength();
		lpsz = rString.GetBuffer(nMaxSize + nLen) + nLen;
	}

	// remove '\n' from end of string if present
	lpsz = rString.GetBuffer(0);
	nLen = rString.GetLength();
	if (nLen != 0 && lpsz[nLen-1] == '\n')
		rString.GetBufferSetLength(nLen-1);

	return lpszResult != NULL;
}

template<typename CHAR_TYPE>
CHAR_TYPE *CIniParser::GetLine(CFile *pFile, CHAR_TYPE *lpsz, UINT nMax)
{
	// if nMax is negative (such a large number doesn't make sense given today's
	// 2gb address space), then assume it to mean "keep the newline".
	int nStop = (int)nMax < 0 ? -(int)nMax : (int)nMax;

	if(lpsz == NULL)
		return NULL;

	CHAR_TYPE ch;
	int nRead = 0;
	BOOL bEndOfFile = FALSE;

	while (nRead < nStop)
	{
		if(pFile->Read(&ch, sizeof(CHAR_TYPE)) < sizeof(CHAR_TYPE))
		{
			bEndOfFile = TRUE;
			break;;
		}
		
		// stop and end-of-line (trailing '\n' is ignored)
		if (ch == '\n' || ch == '\r')
		{
			if (ch == '\r')
			{
				if(pFile->Read(&ch, sizeof(CHAR_TYPE)) < sizeof(CHAR_TYPE))
				{
					bEndOfFile = TRUE;
					break;
				}
			}
			// store the newline when called with negative nMax
			if ((int)nMax != nStop)
				lpsz[nRead++] = ch;
			break;
		}
		lpsz[nRead++] = ch;
	}

	if(bEndOfFile && nRead == 0)
	{
		return NULL;
	}

	lpsz[nRead] = '\0';
	return lpsz;
};

template<typename STRING_TYPE>
CString CIniParser::ConvertString(STRING_TYPE &rString)
{
	STRING_TYPE::PCXSTR lpSource = rString;
	LPCTSTR lpConverted = (LPCTSTR)CStringConvert(lpSource);
	if(lpConverted == (void *)lpSource)
	{
		return CString(rString);
	}
	else
	{
		return lpConverted;
	}
}